package search.fitnessfunctions.util;

import gpinterpreter.stack.StackInstruction;
import gpinterpreter.stack.StackInterpreter;
import gpinterpreter.vector.VecInstruction;
import gpinterpreter.vector.VecInterpreter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jgap.Gene;
import org.jgap.IChromosome;
import org.jgap.impl.IntegerGene;
import primitives.cluster.ClusterHead;
import primitives.cluster.ClusterNode;
import primitives.cluster.ClusterUtils;
import primitives.cluster.IClusterLevel;
import primitives.graph.Node;
import search.fitnessfunctions.TreeFitnessFunction;
import search.genes.BitFieldInstructionGene;
import search.genes.GeneType;
import search.genes.InstructionGene;
import search.genes.IntegerInstructionGene;
import search.genes.StackInstructionGene;

/**
 *
 * @author mat
 */
public abstract class TreeTranslator {

    public static ClusterHead getTree(IChromosome in, ClusterHead originalTree) {
        GeneType type = (GeneType) in.getApplicationData();
        List<Gene> prog = new ArrayList<Gene>();
        for(int i =0; i < in.size(); i++){
            prog.add(in.getGene(i));
        }
        
        return getTree(prog,originalTree, type);


    }
    
    public static ClusterHead getTree(List<Gene> in, ClusterHead originalTree, GeneType type){
        
        switch (type) {
            case INTEGER:
            case SYMBOL:
            case BITFIELD:
                return convertInstructionGene((List)in, originalTree);
            case STACK:
                return convertStackGene((List)in, originalTree);
            case FLAT:
                return convertFlatGene((List)in, originalTree);
            case HCLUST:
                return convertHClustGene((List) in, originalTree);
            default:
                throw new RuntimeException(String.format("Gene Type \"%s\" is not recognised.", type));
        }
    }

    public static ClusterHead convertInstructionGene(List<InstructionGene> program, ClusterHead originalTree) {
        VecInterpreter interp = new VecInterpreter(new ArrayList<VecInstruction>(), originalTree.deepCopy());

        int size = originalTree.getSize();

        VecInstruction inst = null;

        for (int i = 0; i < program.size(); i++) {

            Gene g = program.get(i);

            if (g instanceof IntegerGene) {
                IntegerGene ig = (IntegerGene) g;
                inst = IntegerInstructionGene.getInstruction(ig.intValue(),
                        size);
            } else if (g instanceof InstructionGene) {
                inst = ((InstructionGene) g).getInstruction();
            }

            interp.execute(inst);
        }
        return interp.getTree();
    }
       
    public static ClusterHead convertStackGene(List<StackInstructionGene> program, ClusterHead originalTree){
        List<StackInstruction> conv = new ArrayList<StackInstruction>();
        for(int i= 0; i< program.size(); i++){
            conv.add(program.get(i).getInstruction());
        }
        return convertStackInstruction(conv, originalTree);
    }

    public static ClusterHead convertStackInstruction(List<StackInstruction> program, ClusterHead originalTree) {
        StackInterpreter interp = new StackInterpreter(program, originalTree.deepCopy());
        return interp.run();
    }

    public static ClusterHead convertFlatGene(List<IntegerGene> assignments, ClusterHead originalTree) {
        Map<Integer, Node> nodenumbers = new HashMap<Integer, Node>();

        for (IClusterLevel n : originalTree.getChildren()) {
            if (n.encapsulates()) {
                for (int x : n.getIDs()) {
                    if (x > 0) { //children start at 1, clusterhead is always 0
                        for (Node node : n.getNodes()) {
                            nodenumbers.put(x, node);
                        }
                    }
                }
            }
        }

        ClusterHead ch = new ClusterHead();
        Map<Integer, ClusterNode> parents = new HashMap<Integer, ClusterNode>();
        Set<Node> nodes = originalTree.getNodes();
        for (int i = 0; i < assignments.size(); i++) {
            int parentNumber = assignments.get(i).intValue();
            Node cur = nodenumbers.get(i + 1);



            if (cur == null) {
                throw new RuntimeException(String.format("ERROR: Missing node %d, known nodes: %s", i, nodenumbers.keySet()));
            }
            ClusterNode parent = parents.get(parentNumber);
            if (parent == null) {
                parent = new ClusterNode();
                parent.setID(parentNumber);
                ch.addChild(parent);
                parents.put(parentNumber, parent);
            }
            parent.addNode(cur);
            nodes.remove(cur);


        }
        if (!nodes.isEmpty()) {
            throw new RuntimeException(String.format("%d  nodes weren't added to the new tree: {1}, ", nodes.size(), nodes.toString()));
        }
        return ch;
    }

    public static ClusterHead convertInstructionGene(IChromosome in, ClusterHead originalTree) {
        ArrayList<InstructionGene> prog = new ArrayList<InstructionGene>();
        for (int i = 0; i < in.size(); i++) {

            prog.add((InstructionGene) in.getGene(i));
        }
        return convertInstructionGene(prog, originalTree);
    }

    public static ClusterHead convertStackGene(IChromosome in, ClusterHead originalTree) {
        ArrayList<StackInstruction> prog = new ArrayList<StackInstruction>();

        for (int i = 0; i < in.size(); i++) {
            prog.add(((StackInstructionGene) in.getGene(i)).getInstruction());
        }

        return convertStackInstruction(prog, originalTree);
    }
    


    public static ClusterHead convertFlatGene(IChromosome in, ClusterHead originalTree) {
        ArrayList<IntegerGene> assignments = new ArrayList<IntegerGene>();
        for (int i = 0; i < in.size(); i++) {
            assignments.add((IntegerGene) in.getGene(i));
        }
        return convertFlatGene(assignments, originalTree);
    }

    private static ClusterHead convertHClust(List<Integer> assignments, ClusterHead originalTree){
        Map<Integer, Node> nodenumbers = new HashMap<Integer, Node>();

        for (IClusterLevel n : originalTree.getChildren()) {
            if (n.encapsulates()) {
                for (int x : n.getIDs()) {
                    if (x > 0) { //children start at 1, clusterhead is always 0
                        for (Node node : n.getNodes()) {
                            nodenumbers.put(x, node);
                        }
                    }
                }
            }
        }
        
        ClusterHead newTree = new ClusterHead();
        Map<Integer,ClusterNode> treenodes = new HashMap<Integer,ClusterNode>();
        treenodes.put(0,newTree);
                
        //allocate each node to its parent:
        for(int i = 0; i<originalTree.getNodes().size(); i++){
            int parent = assignments.get(i);
            ClusterNode parentNode = treenodes.get(parent);
            if(parentNode == null){
                parentNode = new ClusterNode();
                treenodes.put(parent, parentNode);
            }
            parentNode.addNode(nodenumbers.get(i+1));
            
        }
        //state is now that there are loads of clusters with the children they need.
        //build the tree:
        int treeSize = originalTree.getNodes().size();
        for(int i = 0; i<assignments.size() - treeSize; i++){
            int parent = assignments.get(i + treeSize); //pull parent id for child i from the array.
            if(parent == i+1){
                //Logger.getLogger(TreeTranslator.class.getName()).log(Level.INFO, "Attempted to make a child a parent of itself {0} from {1}",new Object[]{parent,i});
                parent = 0;
            }
            ClusterNode parentNode = treenodes.get(parent); //parent is whatever that value was
            if(parentNode == null){
                //add intermediate parent that didn't have any nodes in it:
                parentNode = new ClusterNode(); 
                treenodes.put(parent,parentNode);
            }
            ClusterNode childNode = treenodes.get(i + 1); //i starts 0..n-1, they're numbered 1..n (0 is always reserved for the child).
            if(childNode == null){
                childNode = new ClusterNode();
                treenodes.put(i+1, childNode);
            }
            parentNode.addChild(childNode);
            if(ClusterUtils.isCyclic(parentNode)){
                parentNode.deleteChild(childNode);
                //Logger.getLogger(TreeTranslator.class.getName()).log(Level.INFO, "{0} was cyclic so the child {1} was removed", new Object[]{parentNode, childNode});
                
            }
        }
        
        Set<ClusterNode> orphans = new HashSet<ClusterNode>();
        

        for(ClusterNode n : treenodes.values()){
            if(n != newTree && ClusterUtils.findParent(n, newTree) == null){
                orphans.add(n);
            }
        }

        
        for (Iterator<ClusterNode> it = orphans.iterator(); it.hasNext();) {
            ClusterNode clusterNode = it.next();
            
            boolean remove = false;
            for(ClusterNode cn : orphans){
                if(cn != clusterNode && ClusterUtils.findParent(clusterNode, cn) != null){
                    //if the current clusterNode can be found in any of the "trees" of the orphans
                    //then it isn't an orphan proper, just an orphan of the main ClusterHead - we can
                    //fix this problem by adopting its parent, so we remove it from our fixups.
                    remove = true;
                    break;
                }
            }
            if(remove)  it.remove();
        }
        

        //orphans should now contain all clusters that need to be adopted:
        for(ClusterNode cn : orphans){
            if(ClusterUtils.isCyclic(cn)){
                throw new RuntimeException("A cyclic orphan was detected.");   
            }
            newTree.addChild(cn);
        }
        if(ClusterUtils.isCyclic(newTree)){
            Logger.getLogger(TreeTranslator.class.getName()).log(Level.WARNING, "Cyclic tree detected!");
            throw new RuntimeException("There was a cyclic tree so I stopped.");
        }
        //ClusterUtils.prettyPrintTree(newTree);
        Set<Node> oNodes = originalTree.getNodes();
        Set<Node> tNodes = newTree.getNodes();
        
        
        if(!oNodes.containsAll(tNodes) || !tNodes.containsAll(oNodes)){
            Logger.getLogger(TreeTranslator.class.getName()).log(Level.INFO, "Converting a HCLUST: not all nodes were copied, Have {0} nodes but needed {1}.", new Object[]{tNodes.size(), oNodes.size()});
            oNodes.removeAll(tNodes);
            Logger.getLogger(TreeTranslator.class.getName()).log(Level.INFO, "Missing nodes are: {0}", new Object[]{oNodes});
        }
        
        return newTree;
    }
    private static ClusterHead convertHClustGene(List<IntegerGene> assignments, ClusterHead originalTree) {
        ArrayList<Integer> chromosome = new ArrayList<Integer>();
        for(int i =0; i< assignments.size(); i++){
            chromosome.add(assignments.get(i).intValue());
        }
        return convertHClust(chromosome, originalTree);
        
    }
}
